|
1
|
|
|
/*! |
|
2
|
|
|
* @name ElkArte Forum |
|
3
|
|
|
* @copyright ElkArte Forum contributors |
|
4
|
|
|
* @license BSD http://opensource.org/licenses/BSD-3-Clause |
|
5
|
|
|
* |
|
6
|
|
|
* @version 1.1 |
|
7
|
|
|
*/ |
|
8
|
|
|
|
|
9
|
|
|
/** |
|
10
|
|
|
* This file contains javascript associated with the atwho function as it |
|
11
|
|
|
* relates to an sceditor invocation |
|
12
|
|
|
*/ |
|
13
|
|
|
var disableDrafts = false; |
|
14
|
|
|
|
|
15
|
|
|
(function($, window, document) { |
|
|
|
|
|
|
16
|
|
|
'use strict'; |
|
17
|
|
|
|
|
18
|
|
|
// Editor instance |
|
19
|
|
|
var editor, |
|
20
|
|
|
rangeHelper; |
|
21
|
|
|
|
|
22
|
|
|
function elk_Mentions(options) { |
|
23
|
|
|
// All the passed options and defaults are loaded to the opts object |
|
24
|
|
|
this.opts = $.extend({}, this.defaults, options); |
|
|
|
|
|
|
25
|
|
|
} |
|
26
|
|
|
|
|
27
|
|
|
elk_Mentions.prototype.attachAtWho = function(oMentions, $element, oIframeWindow) { |
|
28
|
|
|
var mentioned = $('#mentioned'); |
|
29
|
|
|
|
|
30
|
|
|
// Create / use a container to hold the results |
|
31
|
|
|
if (mentioned.length === 0) |
|
32
|
|
|
$('#' + oMentions.opts.editor_id).after(oMentions.opts._mentioned); |
|
33
|
|
|
else |
|
34
|
|
|
oMentions.opts._mentioned = mentioned; |
|
35
|
|
|
|
|
36
|
|
|
oMentions.opts.cache.mentions = this.opts._mentioned; |
|
37
|
|
|
|
|
38
|
|
|
$element.atwho({ |
|
39
|
|
|
at: "@", |
|
40
|
|
|
limit: 8, |
|
41
|
|
|
maxLen: 25, |
|
42
|
|
|
displayTpl: "<li data-value='${atwho-at}${name}' data-id='${id}'>${name}</li>", |
|
43
|
|
|
acceptSpaceBar: true, |
|
44
|
|
|
callbacks: { |
|
45
|
|
|
filter: function (query, items, search_key) { |
|
|
|
|
|
|
46
|
|
|
// Already cached this query, then use it |
|
47
|
|
|
if (typeof oMentions.opts.cache.names[query] !== 'undefined') { |
|
48
|
|
|
return oMentions.opts.cache.names[query]; |
|
49
|
|
|
} |
|
50
|
|
|
|
|
51
|
|
|
return []; |
|
52
|
|
|
}, |
|
53
|
|
|
// Well then lets make a find member suggest call |
|
54
|
|
|
remoteFilter: function(query, callback) { |
|
55
|
|
|
// Let be easy-ish on the server, don't go looking until we have at least two characters |
|
56
|
|
|
if (query.length < 2) |
|
57
|
|
|
return; |
|
58
|
|
|
|
|
59
|
|
|
// No slamming the server either |
|
60
|
|
|
var current_call = parseInt(new Date().getTime() / 1000); |
|
61
|
|
|
if (oMentions.opts._last_call !== 0 && oMentions.opts._last_call + 0.5 > current_call) { |
|
62
|
|
|
callback(oMentions.opts._names); |
|
63
|
|
|
return; |
|
64
|
|
|
} |
|
65
|
|
|
|
|
66
|
|
|
// What we want |
|
67
|
|
|
var obj = { |
|
68
|
|
|
"suggest_type": "member", |
|
69
|
|
|
"search": query.php_urlencode(), |
|
70
|
|
|
"time": current_call |
|
71
|
|
|
}; |
|
72
|
|
|
|
|
73
|
|
|
// Make the request |
|
74
|
|
|
suggest(obj, function() { |
|
75
|
|
|
// Update the time gate |
|
76
|
|
|
oMentions.opts._last_call = current_call; |
|
77
|
|
|
|
|
78
|
|
|
// Update the cache with the values for reuse in local filter |
|
79
|
|
|
oMentions.opts.cache.names[query] = oMentions.opts._names; |
|
80
|
|
|
|
|
81
|
|
|
// Update the query cache for use in revalidateMentions |
|
82
|
|
|
oMentions.opts.cache.queries[oMentions.opts.cache.queries.length] = query; |
|
83
|
|
|
|
|
84
|
|
|
callback(oMentions.opts._names); |
|
85
|
|
|
}); |
|
86
|
|
|
}, |
|
87
|
|
|
beforeInsert: function(value, $li) { |
|
88
|
|
|
oMentions.addUID($li.data('id'), $li.data('value')); |
|
89
|
|
|
|
|
90
|
|
|
return value; |
|
91
|
|
|
}, |
|
92
|
|
|
matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) { |
|
93
|
|
|
var _a, _y, match, regexp, space; |
|
94
|
|
|
|
|
95
|
|
|
flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); |
|
96
|
|
|
|
|
97
|
|
|
if (should_startWithSpace) { |
|
98
|
|
|
flag = '(?:^|\\s)' + flag; |
|
99
|
|
|
} |
|
100
|
|
|
|
|
101
|
|
|
// Allow À - ÿ |
|
102
|
|
|
_a = decodeURI("%C3%80"); |
|
103
|
|
|
_y = decodeURI("%C3%BF"); |
|
104
|
|
|
|
|
105
|
|
|
// Allow first last name entry? |
|
106
|
|
|
space = acceptSpaceBar ? "\ " : ""; |
|
107
|
|
|
|
|
108
|
|
|
// regexp = new RegExp(flag + '([^ <>&"\'=\\\\\n]*)$|' + flag + '([^\\x00-\\xff]*)$', 'gi'); |
|
109
|
|
|
regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_" + space + "\\[\\]\'\.\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi'); |
|
110
|
|
|
match = regexp.exec(subtext); |
|
111
|
|
|
|
|
112
|
|
|
if (match) { |
|
113
|
|
|
return match[2] || match[1]; |
|
114
|
|
|
} |
|
115
|
|
|
else { |
|
|
|
|
|
|
116
|
|
|
return null; |
|
117
|
|
|
} |
|
118
|
|
|
}, |
|
119
|
|
|
highlighter: function(li, query) { |
|
120
|
|
|
var regexp; |
|
121
|
|
|
|
|
122
|
|
|
if (!query) |
|
123
|
|
|
return li; |
|
124
|
|
|
|
|
125
|
|
|
// Preg Quote regexp from http://phpjs.org/functions/preg_quote/ |
|
126
|
|
|
query = query.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&'); |
|
127
|
|
|
|
|
128
|
|
|
regexp = new RegExp(">\\s*(\\w*)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig'); |
|
129
|
|
|
return li.replace(regexp, function(str, $1, $2, $3) { |
|
130
|
|
|
return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <'; |
|
131
|
|
|
}); |
|
132
|
|
|
}, |
|
133
|
|
|
beforeReposition: function (offset) { |
|
134
|
|
|
// We only need to adjust when in wysiwyg |
|
135
|
|
|
if (editor.inSourceMode()) |
|
136
|
|
|
return offset; |
|
137
|
|
|
|
|
138
|
|
|
// Lets get the caret position so we can add the mentions box there |
|
139
|
|
|
var corrected_offset = findAtPosition(); |
|
140
|
|
|
|
|
141
|
|
|
offset.top = corrected_offset.top; |
|
142
|
|
|
offset.left = corrected_offset.left; |
|
143
|
|
|
|
|
144
|
|
|
return offset; |
|
145
|
|
|
} |
|
146
|
|
|
} |
|
147
|
|
|
}); |
|
148
|
|
|
|
|
149
|
|
|
// Use atwho selection box show/hide events to prevent autosave from firing |
|
150
|
|
|
$(oIframeWindow).on("shown.atwho", function(event, offset) { |
|
|
|
|
|
|
151
|
|
|
disableDrafts = true; |
|
152
|
|
|
}); |
|
153
|
|
|
|
|
154
|
|
|
$(oIframeWindow).on("hidden.atwho", function(event, offset) { |
|
|
|
|
|
|
155
|
|
|
disableDrafts = false; |
|
156
|
|
|
}); |
|
157
|
|
|
|
|
158
|
|
|
/** |
|
159
|
|
|
* Makes the ajax call for data, returns to callback function when done. |
|
160
|
|
|
* |
|
161
|
|
|
* @param obj values to pass to action suggest |
|
162
|
|
|
* @param callback function to call when we have completed our call |
|
163
|
|
|
*/ |
|
164
|
|
|
function suggest(obj, callback) |
|
165
|
|
|
{ |
|
166
|
|
|
var postString = "jsonString=" + JSON.stringify(obj) + "&" + elk_session_var + "=" + elk_session_id; |
|
|
|
|
|
|
167
|
|
|
|
|
168
|
|
|
oMentions.opts._names = []; |
|
169
|
|
|
|
|
170
|
|
|
$.ajax({ |
|
171
|
|
|
url: elk_scripturl + "?action=suggest;xml", |
|
|
|
|
|
|
172
|
|
|
type: "post", |
|
173
|
|
|
data: postString, |
|
174
|
|
|
dataType: "xml" |
|
175
|
|
|
}) |
|
176
|
|
|
.done(function(data) { |
|
177
|
|
|
$(data).find('item').each(function (idx, item) { |
|
178
|
|
|
if (typeof oMentions.opts._names[oMentions.opts._names.length] === 'undefined') |
|
179
|
|
|
oMentions.opts._names[oMentions.opts._names.length] = {}; |
|
180
|
|
|
|
|
181
|
|
|
oMentions.opts._names[oMentions.opts._names.length - 1] = { |
|
182
|
|
|
"id": $(item).attr('id'), |
|
183
|
|
|
"name": $(item).text() |
|
184
|
|
|
}; |
|
185
|
|
|
}); |
|
186
|
|
|
|
|
187
|
|
|
callback(); |
|
188
|
|
|
}) |
|
189
|
|
|
.fail(function(jqXHR, textStatus, errorThrown) { |
|
190
|
|
|
if ('console' in window) { |
|
191
|
|
|
window.console.info('Error:', textStatus, errorThrown.name); |
|
192
|
|
|
window.console.info(jqXHR.responseText); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
callback(); |
|
196
|
|
|
}); |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
/** |
|
200
|
|
|
* Determine the caret position inside of sceditor's iframe |
|
201
|
|
|
* |
|
202
|
|
|
* What it does: |
|
203
|
|
|
* - Caret.js does not seem to return the correct position for (FF & IE) when |
|
204
|
|
|
* the iframe has vertically scrolled. |
|
205
|
|
|
* - This is an sceditor specific function to return a screen caret position |
|
206
|
|
|
* - Called just before At.js adds the mentions dropdown box |
|
207
|
|
|
* - Finds the @mentions tag and adds an invisible zero width space before it |
|
208
|
|
|
* - Gets the location offset() in the iframe "window" of the added space |
|
209
|
|
|
* - Adjusts for the iframe scroll |
|
210
|
|
|
* - Adds in the iframe container location offset() to main window |
|
211
|
|
|
* - Removes the space, restores the editor range. |
|
212
|
|
|
* |
|
213
|
|
|
* @returns {{}} |
|
214
|
|
|
*/ |
|
215
|
|
|
function findAtPosition() { |
|
216
|
|
|
// Get sceditor's RangeHelper for use |
|
217
|
|
|
rangeHelper = editor.getRangeHelper(); |
|
218
|
|
|
|
|
219
|
|
|
// Save the current state |
|
220
|
|
|
rangeHelper.saveRange(); |
|
221
|
|
|
|
|
222
|
|
|
var start = rangeHelper.getMarker('sceditor-start-marker'), |
|
223
|
|
|
parent = start.parentNode, |
|
224
|
|
|
prev = start.previousSibling, |
|
225
|
|
|
offset = {}, |
|
226
|
|
|
atPos, |
|
227
|
|
|
placefinder; |
|
228
|
|
|
|
|
229
|
|
|
// Create a placefinder span containing a 'ZERO WIDTH SPACE' Character |
|
230
|
|
|
placefinder = start.ownerDocument.createElement('span'); |
|
231
|
|
|
$(placefinder).text("200B").addClass('placefinder'); |
|
232
|
|
|
|
|
233
|
|
|
// Look back and find the mentions @ tag, so we can insert our span ahead of it |
|
234
|
|
|
while (prev) { |
|
235
|
|
|
atPos = (prev.nodeValue || '').lastIndexOf('@'); |
|
236
|
|
|
|
|
237
|
|
|
// Found the start of @mention |
|
238
|
|
|
if (atPos > -1) { |
|
239
|
|
|
parent.insertBefore(placefinder, prev.splitText(atPos + 1)); |
|
240
|
|
|
break; |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
prev = prev.previousSibling; |
|
244
|
|
|
} |
|
245
|
|
|
|
|
246
|
|
|
// If we were successful in adding the placefinder |
|
247
|
|
|
if (placefinder.parentNode) { |
|
248
|
|
|
var $_placefinder = $(placefinder); |
|
249
|
|
|
|
|
250
|
|
|
// offset() returns the top offset inside the total iframe, so we need the vertical scroll |
|
251
|
|
|
// value to adjust back to main window position |
|
252
|
|
|
// wizzy_height = $('#' + oMentions.opts.editor_id).parent().find('iframe').height(), |
|
253
|
|
|
// wizzy_window = $('#' + oMentions.opts.editor_id).parent().find('iframe').contents().height(), |
|
254
|
|
|
var wizzy_scroll = $('#' + oMentions.opts.editor_id).parent().find('iframe').contents().scrollTop(); |
|
255
|
|
|
|
|
256
|
|
|
// Determine its Location in the iframe |
|
257
|
|
|
offset = $_placefinder.offset(); |
|
258
|
|
|
|
|
259
|
|
|
// If we have scrolled, then we also need to account for those offsets |
|
260
|
|
|
offset.top -= wizzy_scroll; |
|
261
|
|
|
offset.top += $_placefinder.height(); |
|
262
|
|
|
|
|
263
|
|
|
// Remove our placefinder |
|
264
|
|
|
$_placefinder.remove(); |
|
265
|
|
|
} |
|
266
|
|
|
|
|
267
|
|
|
// Put things back just like we found them |
|
268
|
|
|
rangeHelper.restoreRange(); |
|
269
|
|
|
|
|
270
|
|
|
// Add in the iframe's offset to get the final location. |
|
271
|
|
|
if (offset) { |
|
272
|
|
|
var iframeOffset = editor.getContentAreaContainer().offset(); |
|
273
|
|
|
|
|
274
|
|
|
// Some fudge for the kids |
|
275
|
|
|
offset.top += iframeOffset.top + 5; |
|
276
|
|
|
offset.left += iframeOffset.left + 5; |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
return offset; |
|
280
|
|
|
} |
|
281
|
|
|
}; |
|
282
|
|
|
|
|
283
|
|
|
elk_Mentions.prototype.addUID = function(user_id, name) { |
|
284
|
|
|
this.opts._mentioned.append($('<input type="hidden" name="uid[]" />').val(user_id).attr('data-name', name)); |
|
285
|
|
|
}; |
|
286
|
|
|
|
|
287
|
|
|
/** |
|
288
|
|
|
* Private mention vars |
|
289
|
|
|
*/ |
|
290
|
|
|
elk_Mentions.prototype.defaults = { |
|
291
|
|
|
_names: [], |
|
292
|
|
|
_last_call: 0, |
|
293
|
|
|
_mentioned: $('<div id="mentioned" style="display: none;" />') |
|
294
|
|
|
}; |
|
295
|
|
|
|
|
296
|
|
|
/** |
|
297
|
|
|
* Holds all current mention (defaults + passed options) |
|
298
|
|
|
*/ |
|
299
|
|
|
elk_Mentions.prototype.opts = {}; |
|
300
|
|
|
|
|
301
|
|
|
/** |
|
302
|
|
|
* Mentioning plugin interface to SCEditor |
|
303
|
|
|
* - Called from the editor as a plugin |
|
304
|
|
|
* - Monitors events so we control the elk_mention |
|
305
|
|
|
*/ |
|
306
|
|
|
$.sceditor.plugins.mention = function() { |
|
307
|
|
|
var base = this, |
|
308
|
|
|
oMentions; |
|
309
|
|
|
|
|
310
|
|
|
base.init = function() { |
|
311
|
|
|
// Grab this instance for use use in oMentions |
|
312
|
|
|
editor = this; |
|
313
|
|
|
}; |
|
314
|
|
|
|
|
315
|
|
|
/** |
|
316
|
|
|
* Initialize, called when sceditor starts and initializes plugins |
|
317
|
|
|
*/ |
|
318
|
|
|
base.signalReady = function() { |
|
319
|
|
|
// Init the mention instance, load in the options |
|
320
|
|
|
oMentions = new elk_Mentions(this.opts.mentionOptions); |
|
321
|
|
|
|
|
322
|
|
|
var $option_eid = $('#' + oMentions.opts.editor_id); |
|
323
|
|
|
|
|
324
|
|
|
// Adds the selector to the list of known "mentioner" |
|
325
|
|
|
add_elk_mention(oMentions.opts.editor_id, {isPlugin: true}); |
|
326
|
|
|
oMentions.attachAtWho(oMentions, $option_eid.parent().find('textarea')); |
|
327
|
|
|
|
|
328
|
|
|
// Using wysiwyg, then lets attach atwho to it |
|
329
|
|
|
var instance = $option_eid.sceditor('instance'); |
|
330
|
|
|
if (!instance.opts.runWithoutWysiwygSupport) |
|
331
|
|
|
{ |
|
332
|
|
|
// We need to monitor the iframe window and body to text input |
|
333
|
|
|
var oIframe = $option_eid.parent().find('iframe')[0], |
|
334
|
|
|
oIframeWindow = oIframe.contentWindow, |
|
335
|
|
|
oIframeBody = $(oIframe.contentDocument.body); |
|
336
|
|
|
|
|
337
|
|
|
oMentions.attachAtWho(oMentions, oIframeBody, oIframeWindow); |
|
338
|
|
|
} |
|
339
|
|
|
}; |
|
340
|
|
|
}; |
|
341
|
|
|
})(jQuery, window, document); |
This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.